<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>深入响应式原理</title>
    <script src="../vue.js"></script>
  </head>
  <body>
    <!-- 1 如何追踪变化 -->
    <!-- 2 检测变化的注意事项 -->
    <!-- 2.1 对于对象 -->
    <div id="app1" style="border: 1px dashed red;margin-bottom: 2px;">
      <div>a: {{a}} - b:</div>
      <div>{{$data}}</div>
    </div>
    <!-- 2.2 对于数组 -->
    <div id="app2" style="border: 1px dashed blue;margin-bottom: 2px;">
      <div>{{$data}}</div>
    </div>
    <!-- 3 声明响应式 property -->
    <div id="app3"></div>
    <!-- 4 异步更新队列 -->
    <div id="app4" style="border: 1px dashed blue;margin-bottom: 2px;">
      {{msg}}
    </div>
    <div id="app5" style="border: 1px dashed red;margin-bottom: 2px;">
      <cmt5></cmt5>
    </div>
    
    <script type="text/javascript">
      // 1 如何追踪变化
      // 2 检测变化的注意事项
      //   由于 JavaScript 的限制，Vue 不能检测数组和对象的变化。尽管如此我们还是有一些办法来回避这些限制并保证它们的响应性。
      // 2.1 对于对象
      //   Vue 无法检测 property 的添加或移除。
      //   由于 Vue 会在初始化实例时对 property 执行 getter/setter 转化，
      //   所以 property 必须在 data 对象上存在才能让 Vue 将它转换为响应式的。例如：
      var vm = new Vue({
        el: '#app1',
        data: {
          a: 1,
          someObject: {
            c: 3
          }
        }
      })
      // `vm.a` 是响应式的，`vm.b` 是非响应式的，但如果其他响应式的属性变更时，`vm.b` 也会响应，跟着变更
      /*
      vm.b 报错：Property or method "b" is not defined on the instance but referenced during render. 
                 Make sure that this property is reactive, either in the data option, 
                 or for class-based components, by initializing the property
      */
      vm.b = 2

      //   对于已经创建的实例，Vue 不允许动态添加根级别的响应式 property。
      //   但是，可以使用 Vue.set(object, propertyName, value) 方法向嵌套对象添加响应式 property。例如，对于：
      Vue.set(vm.someObject, 'd', 4)
      //   您还可以使用 vm.$set 实例方法，这也是全局 Vue.set 方法的别名：this.$set(this.someObject,'b',2)
      var e = 'ee'
      vm.$set(vm.someObject, e, 6)

      /* 有时你可能需要为已有对象赋值多个新 property，比如使用 Object.assign() 或 _.extend()。
      但是，这样添加到对象上的新 property 不会触发更新。在这种情况下，
      你应该用原对象与要混合进去的对象的 property 一起创建一个新的对象。 */
      //   代替 `Object.assign(this.someObject, { a: 1, b: 2 })`
      //     如果是在vm内部，则可以这么写：this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })
      vm.someObject = Object.assign({}, vm.someObject, {h: 10, i: 11})

      // 2.2 对于数组
      var vm2 = new Vue({
        el: '#app2',
        data: {
          items: ['aa', 'bb', 'cc']
        }
      })
      vm2.items[1] = 'aa2' // 不是响应性的，但如果其他响应式的属性变更时，它也会响应，跟着变更
      vm2.items.length = 2 // 不是响应性的，但如果其他响应式的属性变更时，它也会响应，跟着变更
      //   为了解决第一类问题，以下两种方式都可以实现和 vm.items[indexOfItem] = newValue 相同的效果，
      //   同时也将在响应式系统内触发状态更新：
      //     Vue.set: Vue.set(vm2.items, indexOfItem, newValue)
      Vue.set(vm2.items, 1, 'bb3')

      //     Array.prototype.splice: vm2.items.splice(indexOfItem, 1, newValue)
      vm2.items.splice(1, 1, 'bb5')
      //     你也可以使用 vm.$set 实例方法，该方法是全局方法 Vue.set 的一个别名：vm2.$set(vm2.items, indexOfItem, newValue)
      vm2.$set(vm2.items, 0, 'aa2')

      //   为了解决第二类问题，你可以使用 splice：
      vm2.items.splice(1)
      
      // 3 声明响应式 property
      //   由于 Vue 不允许动态添加根级响应式 property，
      //   所以你必须在初始化实例前声明所有根级响应式 property，哪怕只是一个空值：
      var vm3 = new Vue({
        el: '#app3',
        data: {
          // 声明 message 为一个空值字符串
          message: ''
        },
        template: '<div style="border: 1px dashed red;margin-bottom: 2px;">message: {{ message }}</div>'
      })
      // 之后设置 `message`
      vm3.message = 'Hello!'
      
      // 4 异步更新队列
      /* 为了在数据变化之后等待 Vue 完成更新 DOM，
      可以在数据变化之后立即使用 Vue.nextTick(callback)。
      这样回调函数将在 DOM 更新完成后被调用 */
      var vm4 = new Vue({
        el: '#app4',
        data: {
          msg: '123'
        }
      })
      vm4.msg = 'new msg' // 更改数据
      console.log(vm4.$el.textContent === 'new msg') // false
      Vue.nextTick(function() {
        console.log(vm4.$el.textContent === 'new msg') // true
      })
      /* 在组件内使用 vm.$nextTick() 实例方法特别方便，因为它不需要全局 Vue，
      并且回调函数中的 this 将自动绑定到当前的 Vue 实例上： */
      
      Vue.component('cmt5', {
        template: '<span>{{ message }}</span>',
        data: function() {
          return {
            message: '未更新'
          }
        },
        methods: {
          // 如何调用该方法？？？
          updateMessage: function() {
            this.message = '已更新'
            console.log(this.$el.textContent) // => '未更新'
            this.$nextTick(function() {
              console.log(this.$el.textContent) // => '已更新'
            })
          },
          // 因为 $nextTick() 返回一个 Promise 对象，所以你可以使用新的 ES2017 async/await 语法完成相同的事情：
          // 如何调用该方法？？？
          updateMessage2: async function () {
            this.message = '已更新'
            console.log(this.$el.textContent) // => '未更新'
            await this.$nextTick()
            console.log(this.$el.textContent) // => '已更新'
          }
        }
      })
      
      var vm5 = new Vue({
        el: '#app5'
      })
    </script>
  </body>
</html>
